﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal class GroupHandler : HandlerBase
  {
    #region Constructor

    internal GroupHandler()
    {
      m_groupCollections = new GroupLevelCollection( m_groupDescriptions );
      m_groupCollections.CollectionChanged += new NotifyCollectionChangedEventHandler( this.OnGroupCollectionsCollectionChanged );
      m_groupCollections.ExpansionChanging += new EventHandler<ExpansionChangedEventArgs>( this.OnGroupCollectionsExpansionChanging );
      m_groupCollections.ExpansionChanged += new EventHandler<ExpansionChangedEventArgs>( this.OnGroupCollectionsExpansionChanged );
    }

    #endregion

    #region GroupHeadersFootersSelector Property

    internal IGroupHeadersFootersSelector GroupHeadersFootersSelector
    {
      get;
      set;
    }

    #endregion

    #region GroupStatFunctionsSelector Property

    internal IGroupStatFunctionsSelector GroupStatFunctionsSelector
    {
      get;
      set;
    }

    #endregion

    #region GroupDescriptions Property

    internal ObservableCollection<GroupDescription> GroupDescriptions
    {
      get
      {
        return m_groupDescriptions;
      }
    }

    private readonly ObservableCollection<GroupDescription> m_groupDescriptions = new ObservableCollection<GroupDescription>();

    #endregion

    #region GroupCollections Property

    internal GroupLevelCollection GroupCollections
    {
      get
      {
        return m_groupCollections;
      }
    }

    private readonly GroupLevelCollection m_groupCollections;

    #endregion

    #region GroupCountPolicy Property

    internal GroupCountPolicy GroupCountPolicy
    {
      get
      {
        return m_groupCountPolicy;
      }
      set
      {
        m_groupCountPolicy = value;
      }
    }

    private GroupCountPolicy m_groupCountPolicy = GroupCountPolicy.DoNotHandleCounts;

    #endregion

    #region IncludeGroupItems Property

    internal bool IncludeGroupItems
    {
      get
      {
        return m_includeGroupItems;
      }
      set
      {
        m_includeGroupItems = value;
      }
    }

    private bool m_includeGroupItems; //false

    #endregion

    #region IncludeGroupItemsCount Property

    internal bool IncludeGroupItemsCount
    {
      get
      {
        return m_includeGroupItemsCount;
      }
      set
      {
        m_includeGroupItemsCount = value;
      }
    }

    private bool m_includeGroupItemsCount; //false

    #endregion

    #region AnyHeaderFooter Property

    internal bool AnyHeaderFooter
    {
      get
      {
        if( ( this.GroupHeadersFootersSelector == null )
          || ( this.GroupDescriptions.Count == 0 ) )
          return false;

        for( int i = 0; i < this.GroupDescriptions.Count; i++ )
        {
          DataPath levelPath = new DataPath( Enumerable.Repeat( GroupDataItem.Any, i + 1 ).ToArray() );

          if( this.GroupHeadersFootersSelector.HasHeaders( levelPath )
            || this.GroupHeadersFootersSelector.HasFooters( levelPath ) )
            return true;
        }

        return false;
      }
    }

    #endregion

    #region AnyCollapsedGroup Property

    internal bool AnyCollapsedGroup
    {
      get
      {
        return m_groupCollections.Any( ( collection ) => collection.AnyCollapsedGroup.GetValueOrDefault( collection.IsCollapsedByDefault ) );
      }
    }

    #endregion

    #region PropertyNames Property

    internal IEnumerable<string> PropertyNames
    {
      get
      {
        return m_groupDescriptions.GetPropertyNames();
      }
    }

    #endregion

    #region ExpansionChanging Event

    internal event EventHandler<ExpansionChangedEventArgs> ExpansionChanging;

    private void RaiseExpansionChanging( ExpansionChangedEventArgs e )
    {
      if( this.ExpansionChanging != null )
      {
        this.ExpansionChanging( this, e );
      }
    }

    #endregion

    #region ExpansionChanged Event

    internal event EventHandler<ExpansionChangedEventArgs> ExpansionChanged;

    private void RaiseExpansionChanged( ExpansionChangedEventArgs e )
    {
      if( this.ExpansionChanged != null )
      {
        this.ExpansionChanged( this, e );
      }
    }

    #endregion

    internal bool IsCollapsed( DataPath groupPath )
    {
      return m_groupCollections.IsCollapsedAtTargetLevel( groupPath, groupPath.Depth );
    }

    internal DataPath GetGroupingPath( DataPath completePath )
    {
      int groupingDepth = this.GetGroupingDepth( completePath );

      return ( groupingDepth == completePath.Depth )
        ? completePath
        : completePath.CreateAncestorPath( groupingDepth );
    }

    internal int GetGroupingDepth( DataPath completePath )
    {
      if( completePath == null )
        throw new ArgumentNullException( "completePath" );

      int groupingDepth = 0;

      while( groupingDepth < completePath.Depth )
      {
        if( !( completePath[ groupingDepth ] is GroupDataItem ) )
          break;

        groupingDepth++;
      }

      return groupingDepth;
    }

    internal int GetCollapsedDepth( DataPath path )
    {
      return m_groupCollections.GetCollapsedDepth( path );
    }

    internal IAsyncEnumerator<DataPath> GetHeadersDataPathEnumerator( DataPath groupPath )
    {
      return this.ConvertEnumerator( groupPath, HeaderFooterType.Header, this.GetHeadersFootersEnumerator( groupPath, true ) );
    }

    internal IAsyncEnumerator<DataPath> GetFootersDataPathEnumerator( DataPath groupPath )
    {
      return this.ConvertEnumerator( groupPath, HeaderFooterType.Footer, this.GetHeadersFootersEnumerator( groupPath, false ) );
    }

    private IAsyncEnumerator<DataPath> ConvertEnumerator( DataPath groupPath, HeaderFooterType headerType, IAsyncEnumerator<object> objectHeadersFootersEnum )
    {
      return new HeaderFooterEnumerator( objectHeadersFootersEnum, groupPath, headerType );
    }

    private IAsyncEnumerator<object> GetHeadersFootersEnumerator( DataPath groupPath, bool headers )
    {
      if( groupPath == null )
        throw new ArgumentNullException( "groupPath" );

      if( !( groupPath.LastChild is GroupDataItem ) )
        throw new ArgumentException( "path must refer to a group", "groupPath" );

      IEnumerable<object> headersFooters = null;

      if( this.GroupHeadersFootersSelector != null )
      {
        if( headers )
        {
          headersFooters = this.GroupHeadersFootersSelector.GetHeaders( groupPath );
        }
        else
        {
          headersFooters = this.GroupHeadersFootersSelector.GetFooters( groupPath );
        }
      }

      return new SyncToAsyncEnumerator<object>(
        new LocalListEnumerator<object>( ( headersFooters == null ) ? new List<object>() : headersFooters.ToList() ) );
    }

    private void OnGroupCollectionsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      this.RaiseSourceChanged( new SourceChangedEventArgs( SourceChangedType.GroupingChanged ) );
    }

    private void OnGroupCollectionsExpansionChanging( object sender, ExpansionChangedEventArgs e )
    {
      this.RaiseExpansionChanging( e );
    }

    private void OnGroupCollectionsExpansionChanged( object sender, ExpansionChangedEventArgs e )
    {
      this.RaiseExpansionChanged( e );
    }

    #region HeaderFooterEnumerator Nested Type

    private class HeaderFooterEnumerator : ConversionEnumerator<object, DataPath>
    {
      internal HeaderFooterEnumerator( IAsyncEnumerator childEnumerator, DataPath groupPath, HeaderFooterType headerType )
        : base( childEnumerator )
      {
        if( groupPath == null )
          throw new ArgumentNullException( "groupPath" );

        m_groupPath = groupPath;
        m_type = headerType;
      }

      protected override DataPath Convert( DataSourceContext context, object value )
      {
        return m_groupPath.CreateChildPath( new HeaderFooterDataItem( value, m_type ) );
      }

      protected override object ConvertBack( DataSourceContext context, DataPath value )
      {
        HeaderFooterDataItem dataItem = ( HeaderFooterDataItem )value.LastChild;

        return dataItem.Data;
      }

      private readonly DataPath m_groupPath;
      private readonly HeaderFooterType m_type;
    }

    #endregion
  }
}
